//=============================================================================
//
//		Copyright (C) 1995, 1996 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//		
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableView.M
//
//		General-purpose 2-D display object that works with the
//		MiscTableScroll to provide row/column sizing and dragging.
//
//		This object is responsible for drawing, mouse and keyboard
//		events in the content portion of the display.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableView.M,v 1.8 96/01/13 23:46:17 zarnuk Exp $
// $Log:		MiscTableView.M,v $
//	Revision 1.8  96/01/13	23:46:17  zarnuk
//	All allocations are now done from [self zone].
//	
//	Revision 1.7  95/10/20	00:13:39  sunshine
//	Was including MiscTableScroll.h with "" instead of <>.
//	
//	Revision 1.6  95/10/19	05:42:00  sunshine
//	Now sends -setSelFont: to FontManager at appropriate times so that font
//	selection in FontManager is up-to-date.
//	
//	Revision 1.5  95/10/08	16:12:30  zarnuk
//	Cell* --> id.
//-----------------------------------------------------------------------------
#import "MiscTableView.h"
#import "MiscHighlightTracker.h"
#import "MiscListTracker.h"
#import "MiscRadioTracker.h"
#import "MiscSparseSet.h"
#import "MiscTableBorder.h"
#import "MiscTableFocus.h"
#import <misckit/MiscTableScroll.h>

#import <new.h>

extern "Objective-C" {
#import <appkit/Application.h>
#import <appkit/Cell.h>
#import <appkit/FontManager.h>
#import <appkit/timer.h>
}

extern "C" {
#import <math.h>		// for floor()
}


//----------------------------------------------------------------------------
// startTimer
//----------------------------------------------------------------------------
static inline void startTimer( NXTrackingTimer*& timer )
	{
	if (timer == 0)
		timer = NXBeginTimer( 0, 0.1, 0.1 );
	}


//----------------------------------------------------------------------------
// stopTimer
//----------------------------------------------------------------------------
static inline void stopTimer( NXTrackingTimer*& timer )
	{
	if (timer)
		{
		NXEndTimer( timer );
		timer = 0;
		}
	}



//=============================================================================
// IMPLEMENTATION
//=============================================================================
@implementation MiscTableView

//-----------------------------------------------------------------------------
// - initFrame:
//
//		NOTE *1*: Default behavior is to take keyboard-cursor information from
//				the row-border.
//-----------------------------------------------------------------------------
- initFrame:(NXRect const*)frameRect
		scroll:(MiscTableScroll*)i_scroll
		colInfo:(MiscTableBorder*)i_col_border
		rowInfo:(MiscTableBorder*)i_row_border
	{
	NXZone* const z = [self zone];
	NXRect rect;
	if (frameRect != 0)
		rect.origin = frameRect->origin;
	else
		{
		rect.origin.x = 0;
		rect.origin.y = 0;
		}

	rect.size.width = i_col_border->totalSize();
	rect.size.height = i_row_border->totalSize();

	[super initFrame:&rect];
	[self setFlipped:YES];
	[self setClipping:NO];
	[self setOpaque:YES];

	scroll = i_scroll;
	col_border = i_col_border;
	row_border = i_row_border;
	tracker_border = MISC_ROW_BORDER;					// NOTE *1*

	oldColSel = new( NXZoneMalloc(z,sizeof(*oldColSel)) ) MiscSparseSet;
	oldRowSel = new( NXZoneMalloc(z,sizeof(*oldRowSel)) ) MiscSparseSet;
	[self setSelectionMode: [scroll selectionMode]];

	focuser = [[MiscTableFocus allocFromZone:z] initOwner: self];
	return self;
	}


//-----------------------------------------------------------------------------
// - free
//-----------------------------------------------------------------------------
- free
	{
	[focuser free];
	[tracker free];
	NXZone* const z = [self zone];
	if (oldColSel != 0)
		{
		oldColSel->MiscSparseSet::~MiscSparseSet();
		NXZoneFree( z, oldColSel );
		}
	if (oldRowSel != 0)
		{
		oldRowSel->MiscSparseSet::~MiscSparseSet();
		NXZoneFree( z, oldRowSel );
		}
	return [super free];
	}


//-----------------------------------------------------------------------------
// TYPE VARIATIONS
//-----------------------------------------------------------------------------

- (MiscTableBorder*) borderFor: (MiscBorderType)b
		{ return (b == MISC_ROW_BORDER ? row_border : col_border); }
- (MiscBorderType) otherBorder: (MiscBorderType)b
		{ return (b == MISC_ROW_BORDER ? MISC_COL_BORDER : MISC_ROW_BORDER); }
- (BOOL) isHorz: (MiscBorderType)b { return (b == MISC_COL_BORDER); }

- (NXCoord) border:(MiscBorderType)b pointX: (NXPoint const*) point
		{ return [self isHorz:b] ? point->x : point->y; }
- (NXCoord) border:(MiscBorderType)b pointY: (NXPoint const*) point
		{ return [self isHorz:b] ? point->y : point->x; }

- (NXCoord) border:(MiscBorderType)b rectMinX:(NXRect const*)frameRect
		{ return [self isHorz:b] ? NX_X(frameRect) : NX_Y(frameRect); }
- (NXCoord) border:(MiscBorderType)b rectMaxX:(NXRect const*)frameRect
		{ return [self isHorz:b] ? NX_MAXX(frameRect) : NX_MAXY(frameRect); }
- (NXCoord) border:(MiscBorderType)b rectMinY:(NXRect const*)frameRect
		{ return [self isHorz:b] ? NX_Y(frameRect) : NX_X(frameRect); }
- (NXCoord) border:(MiscBorderType)b rectMaxY:(NXRect const*)frameRect
		{ return [self isHorz:b] ? NX_MAXY(frameRect) : NX_MAXX(frameRect); }
- (NXCoord) border:(MiscBorderType)b rectWidth:(NXRect const*)frameRect
		{ return [self isHorz:b] ? NX_WIDTH(frameRect) : NX_HEIGHT(frameRect); }
- (NXCoord) border:(MiscBorderType)b rectHeight:(NXRect const*)frameRect
		{ return [self isHorz:b] ? NX_HEIGHT(frameRect) : NX_WIDTH(frameRect); }

- (void) border:(MiscBorderType)b setRect:(NXRect*)rect minX:(NXCoord)x
		{ if ([self isHorz:b]) NX_X(rect) = x; else NX_Y(rect) = x; }
- (void) border:(MiscBorderType)b setRect:(NXRect*)rect minY:(NXCoord)y
		{ if ([self isHorz:b]) NX_Y(rect) = y; else NX_X(rect) = y; }
- (void) border:(MiscBorderType)b setRect:(NXRect*)rect width:(NXCoord)width
		{ if ([self isHorz:b]) NX_WIDTH(rect) = width;
				else NX_HEIGHT(rect) = width; }
- (void) border:(MiscBorderType)b setRect:(NXRect*)rect height:(NXCoord)height
		{ if ([self isHorz:b]) NX_HEIGHT(rect) = height;
				else NX_WIDTH(rect) = height; }


//-----------------------------------------------------------------------------
// drewCellAt:border:oldSel:
//
//		Keeps the old selection sets up to date whenever we draw a cell.  This
//		way -reflectSelection has valid data to work from.
//-----------------------------------------------------------------------------
- (void) drewCellAt: (MiscCoord_V)slot border: (MiscTableBorder*)b
	oldSel: (MiscSparseSet*)oldSel
	{
	BOOL isSelected = b->selectionSet().contains( slot );
	if (isSelected != oldSel->contains(slot))
		{
		if (isSelected)
			oldSel->add( slot );
		else
			oldSel->remove( slot );
		}
	}


//-----------------------------------------------------------------------------
// drewCellAt::
//-----------------------------------------------------------------------------
- (void) drewCellAt: (MiscCoord_V)r : (MiscCoord_V)c
	{
	[self drewCellAt:r border:row_border oldSel: oldRowSel];
	[self drewCellAt:c border:col_border oldSel: oldColSel];
	}


//-----------------------------------------------------------------------------
// - getCellFrame:at:: -- Visual coords
//-----------------------------------------------------------------------------
- getCellFrame: (NXRect*)r at: (int)row : (int)col
	{
	if (row < 0 || row >= row_border->count() ||
		col < 0 || col >= col_border->count())
		NXSetRect( r, 0, 0, 0, 0 );
	else
		NXSetRect( r, col_border->getOffset(col), row_border->getOffset(row),
				col_border->effectiveSize(col),row_border->effectiveSize(row) );
	return self;
	}


//-----------------------------------------------------------------------------
// - getVisFrame:at:from:
//-----------------------------------------------------------------------------
- (void) getVisFrame:(NXRect*)r at:(MiscCoord_V)slot from:(MiscBorderType)bdr
	{
	MiscTableBorder* const b = [self borderFor:bdr];
	[self getVisibleRect: r];
	[self border:bdr setRect:r minX:b->getOffset(slot)];
	[self border:bdr setRect:r width:b->effectiveSize(slot)];
	}


//-----------------------------------------------------------------------------
// - firstVisibleSlot:
//-----------------------------------------------------------------------------
- (MiscCoord_V) firstVisibleSlot: (MiscBorderType)bdr
	{
	MiscCoord_V ret = -1;
	MiscTableBorder* const b = [self borderFor:bdr];
	if (b->count() > 0)
		{
		NXRect r; [self getVisibleRect: &r];
		MiscPixels vis_origin = MiscPixels( [self border:bdr rectMinX:&r] );
		ret = b->visualForOffset( vis_origin );
		MiscPixels slot_origin = b->getOffset( ret );
		if (slot_origin < vis_origin)
			ret++;
		}
	return ret;
	}


//-----------------------------------------------------------------------------
// - acceptsFirstMouse
//-----------------------------------------------------------------------------
- (BOOL) acceptsFirstMouse
	{
	return YES;
	}


//-----------------------------------------------------------------------------
// - acceptsFirstResponder
//-----------------------------------------------------------------------------
- (BOOL) acceptsFirstResponder
	{
	return YES;
	}


//-----------------------------------------------------------------------------
// - setFocusFrame
//-----------------------------------------------------------------------------
- (void) setFocusFrame
	{
	NXRect r = {{ 0,0 }, { 0,0 }};
	if (row_border->count() > 0 && col_border->count() > 0)
		{
		MiscTableBorder* const b = [self borderFor: tracker_border];
		MiscCoord_V focusSlot = b->getCursor();
		if (focusSlot < 0 || focusSlot >= b->count())
			focusSlot = (b->hasSelection() ? b->selectedSlot() :
						[self firstVisibleSlot: tracker_border]);
		b->setCursor( focusSlot );
		[self getVisFrame:&r at:focusSlot from:tracker_border];
		}
	[focuser setFrame: &r];
	}


//-----------------------------------------------------------------------------
// - startFocus
//-----------------------------------------------------------------------------
- (void) startFocus
	{
	[self setFocusFrame];
	[focuser startFocus];
	}


//-----------------------------------------------------------------------------
// - stopFocus
//-----------------------------------------------------------------------------
- (void) stopFocus
	{
	[focuser stopFocus];
	}


//-----------------------------------------------------------------------------
// - becomeFirstResponder
//-----------------------------------------------------------------------------
- becomeFirstResponder
	{
	Window* win = [self window];
	if (win && [win isKeyWindow])
		{
		[self startFocus];
		[[FontManager new] setSelFont:[scroll font] isMultiple:NO];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - resignFirstResponder
//-----------------------------------------------------------------------------
- resignFirstResponder
	{
	[self stopFocus];
	return self;
	}


//-----------------------------------------------------------------------------
// - becomeKeyWindow
//-----------------------------------------------------------------------------
- becomeKeyWindow
	{
	[self startFocus];
	[[FontManager new] setSelFont:[scroll font] isMultiple:NO];
	return self;
	}


//-----------------------------------------------------------------------------
// - resignKeyWindow
//-----------------------------------------------------------------------------
- resignKeyWindow
	{
	[self stopFocus];
	return self;
	}


//-----------------------------------------------------------------------------
// - windowChanged:
//-----------------------------------------------------------------------------
- windowChanged: newWindow
	{
	[self stopFocus];
	return self;
	}


//-----------------------------------------------------------------------------
// - resetCursorRects
//
//		NOTE: This catches srolling.  The blinking cursor is always placed
//				within the visibleRect.	 After scrolling the visibleRect has
//				changed so we need to readjust the cursor frame.
//-----------------------------------------------------------------------------
- resetCursorRects
	{
	[self setFocusFrame];
	return self;
	}


//-----------------------------------------------------------------------------
// - sizeTo::
//-----------------------------------------------------------------------------
- sizeTo: (NXCoord)width : (NXCoord)height
	{
	[super sizeTo: width : height];
	[self setFocusFrame];
	return self;
	}


//-----------------------------------------------------------------------------
// - setFrame:
//-----------------------------------------------------------------------------
- setFrame: (NXRect const*)r
	{
	[super setFrame: r];
	[self setFocusFrame];
	return self;
	}


//-----------------------------------------------------------------------------
// - adjustSize
//-----------------------------------------------------------------------------
- (void) adjustSize
	{
	[self sizeTo: col_border->totalSize() : row_border->totalSize()];
	}


//-----------------------------------------------------------------------------
// - scrollCellToVisible:: -- Physical coords
//-----------------------------------------------------------------------------
- (void) scrollCellToVisible: (int)row : (int) col
	{
	NXRect r;
	[self getCellFrame: &r at:
						row_border->physicalToVisual(row) :
						col_border->physicalToVisual(col)];
	[self scrollRectToVisible: &r];
	}


//-----------------------------------------------------------------------------
// - border:scrollToVisible: -- Physical coord
//-----------------------------------------------------------------------------
- (void) border: (MiscBorderType)bdr scrollToVisible: (MiscCoord_P)slot
	{
	NXRect r;
	MiscTableBorder* const b = [self borderFor:bdr];
	[self getVisFrame:&r at:b->physicalToVisual(slot) from:bdr];
	[self scrollRectToVisible:&r];
	}


//-----------------------------------------------------------------------------
// - scrollRowToVisible: -- Physical coord
//-----------------------------------------------------------------------------
- (void) scrollRowToVisible: (int)row
	{
	[self border:MISC_ROW_BORDER scrollToVisible:row];
	}


//-----------------------------------------------------------------------------
// - scrollColToVisible: -- Physical coord
//-----------------------------------------------------------------------------
- (void) scrollColToVisible: (int)col
	{
	[self border:MISC_COL_BORDER scrollToVisible:col];
	}


//-----------------------------------------------------------------------------
// - cMin:cMax:rMin:rMax:forRect:
//-----------------------------------------------------------------------------
- (void) cMin: (MiscCoord_V*)cmin cMax: (MiscCoord_V*)cmax
		 rMin: (MiscCoord_V*)rmin rMax: (MiscCoord_V*)rmax
		 forRect: (NXRect const*) nxrect
	{
	*cmin = col_border->visualForOffset( (MiscPixels) nxrect->origin.x );
	*cmax = col_border->visualForOffset( 
				(MiscPixels) (nxrect->origin.x + nxrect->size.width) - 1 );
	*rmin = row_border->visualForOffset( (MiscPixels) nxrect->origin.y );
	*rmax = row_border->visualForOffset(
				(MiscPixels) (nxrect->origin.y + nxrect->size.height) - 1 );
	}


//-----------------------------------------------------------------------------
// - drawRect:
//-----------------------------------------------------------------------------
- (void) drawRect: (NXRect const*) nxrect
	{
	MiscCoord_V c, cmin, cmax;
	MiscCoord_V r, rmin, rmax;
	[self cMin:&cmin cMax:&cmax rMin:&rmin rMax:&rmax forRect:nxrect];

	if (cmin >= 0 && cmax >= 0 && rmin >= 0 && rmax >= 0)
		{
		NXCoord const x0 = floor( (float) col_border->getOffset( cmin ) );
		NXCoord const y0 = floor( (float) row_border->getOffset( rmin ) );
		NXRect rect = { {x0,y0}, {0,0} };
	
		for (r = rmin;	r <= rmax;	r++)
			{
			rect.size.height = floor( (float) row_border->effectiveSize(r) );
			for (c = cmin;	c <= cmax;	c++)
				{
				rect.size.width = floor( (float)col_border->effectiveSize(c) );
				id cell = [scroll cellAt: row_border->visualToPhysical(r) :
											 col_border->visualToPhysical(c)];
				[cell drawSelf:&rect inView:self];
				[self drewCellAt:r:c];
				rect.origin.x = floor( rect.origin.x + rect.size.width );
				}
			rect.origin.x = x0;
			rect.origin.y = floor( rect.origin.y + rect.size.height );
			}
		}
	}


//-----------------------------------------------------------------------------
// - drawSelf::
//-----------------------------------------------------------------------------
- drawSelf: (NXRect const*) rects :(int) nrects
	{
	if (nrects == 1)
		[self drawRect: rects];
	else if (nrects == 3)
		{
		[self drawRect: ++rects];
		[self drawRect: ++rects];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - drawCellAt::updateSel: -- Physical coords
//-----------------------------------------------------------------------------
- (void) drawCellAt: (int)row : (int)col updateSel: (BOOL)updateSel
	{
	if ([self canDraw] &&
		row >= 0 && row < row_border->count() &&
		col >= 0 && col < col_border->count())
		{
		NXRect v;		[self getVisibleRect: &v];
		MiscCoord_V const vRow = row_border->physicalToVisual(row);
		MiscCoord_V const vCol = col_border->physicalToVisual(col);

		NXRect const r = {
		{ col_border->getOffset(vCol), row_border->getOffset(vRow) },
		{ col_border->effectiveSize(vCol), row_border->effectiveSize(vRow) }};

		if (NXIntersectsRect( &r, &v ))
			{
			id const cell = [scroll cellAt: row : col];
			BOOL const didLock = ![self isFocusView];
			if (didLock) [self lockFocus];
			[cell drawSelf: &r inView: self];
			if (updateSel)
				[self drewCellAt: vRow : vCol];
			if (didLock) [self unlockFocus];
			}
		}
	}


//-----------------------------------------------------------------------------
// - drawCellAt:: -- Physical coords
//-----------------------------------------------------------------------------
- drawCellAt: (int)row : (int)col
	{
	[self drawCellAt:row:col updateSel:YES];
	return self;
	}


//-----------------------------------------------------------------------------
// - drawRow: -- Physical coord
//-----------------------------------------------------------------------------
- drawRow: (int)row
	{
	if ([self canDraw] && row >= 0 && row < row_border->count())
		{
		MiscCoord_V const vRow = row_border->physicalToVisual(row);
	
		NXRect r;
		[self getVisibleRect: &r];
		r.origin.y = row_border->getOffset(vRow);
		r.size.height = row_border->effectiveSize(vRow);

		BOOL const didLock = ![self isFocusView];
		if (didLock) [self lockFocus];
		[self drawRect: &r];
		if (didLock) [self unlockFocus];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - drawCol: -- Physical coord
//-----------------------------------------------------------------------------
- drawCol: (int)col
	{
	if ([self canDraw] && col >= 0 && col < col_border->count())
		{
		MiscCoord_V const vCol = col_border->physicalToVisual(col);
	
		NXRect r;
		[self getVisibleRect: &r];
		r.origin.x = col_border->getOffset(vCol);
		r.size.width = col_border->effectiveSize(vCol);

		BOOL const didLock = ![self isFocusView];
		if (didLock) [self lockFocus];
		[self drawRect: &r];
		if (didLock) [self unlockFocus];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - setSelectionMode:
//-----------------------------------------------------------------------------
- (void) setSelectionMode: (MiscSelectionMode) mode
	{
	NXZone* const z = [self zone];
	if (tracker != 0)
		[tracker free];
	switch (mode)
		{
		case MISC_LIST_MODE:
			tracker = [MiscListTracker allocFromZone:z];
			break;
		case MISC_RADIO_MODE:
			tracker = [MiscRadioTracker allocFromZone:z];
			break;
		case MISC_HIGHLIGHT_MODE:
			tracker = [MiscHighlightTracker allocFromZone:z];
			break;
		}
	[tracker initBorder: [self borderFor:tracker_border]];
	}


//-----------------------------------------------------------------------------
// - reflectSelection
//-----------------------------------------------------------------------------
- (void) reflectSelection
	{
	if ([self canDraw])
		{
		MiscSparseSet const& newColSel = col_border->selectionSet();
		MiscSparseSet const& newRowSel = row_border->selectionSet();

		MiscCoord_V cmin, cmax;
		MiscCoord_V rmin, rmax;
		NXRect vis;		[self getVisibleRect: &vis];
		[self cMin:&cmin cMax:&cmax rMin:&rmin rMax:&rmax forRect:&vis];
	
		[window disableFlushWindow];
		BOOL locked = NO;
		for (MiscCoord_V r = rmin; r <= rmax; r++)
			{
			BOOL rowWasOn = oldRowSel->contains(r);
			BOOL rowIsOn = newRowSel.contains(r);
			for (MiscCoord_V c = cmin; c <= cmax; c++)
				{
				BOOL wasOn = rowWasOn || oldColSel->contains(c);
				BOOL isOn = rowIsOn || newColSel.contains(c);
				if (isOn != wasOn)
					{
					if (!locked)
						{
						locked = YES;
						[self lockFocus];
						}
					MiscCoord_P const pCol = col_border->visualToPhysical(c);
					MiscCoord_P const pRow = row_border->visualToPhysical(r);
					[self drawCellAt: pRow : pCol updateSel: NO];
					}
				}
			}
		if (locked)
			[self unlockFocus];
		[[window reenableFlushWindow] flushWindow];

		*oldColSel = newColSel;
		*oldRowSel = newRowSel;
		}
	}


//-----------------------------------------------------------------------------
// - trackBy:
//-----------------------------------------------------------------------------
- (void) trackBy: (MiscBorderType)b
	{
	tracker_border = b;
	[self setFocusFrame];
	}


//-----------------------------------------------------------------------------
// - trackingBy
//-----------------------------------------------------------------------------
- (MiscBorderType) trackingBy
	{
	return tracker_border;
	}


//-----------------------------------------------------------------------------
// - reflectCursor
//-----------------------------------------------------------------------------
- (void) reflectCursor
	{
	[self setFocusFrame];
	}


//-----------------------------------------------------------------------------
// constrainSlot:inBorder:
//-----------------------------------------------------------------------------
- (MiscCoord_V) constrainSlot:(MiscCoord_V)s inBorder: (MiscTableBorder*)b
	{
	if (s < 0)
		s = 0;
	else if (s >= b->count())
		s = b->count() - 1;
	return s;
	}


//-----------------------------------------------------------------------------
// - mouseDown:
//-----------------------------------------------------------------------------
- mouseDown: (NXEvent*) p
	{
	BOOL doubleClicked = (p->data.mouse.click > 1);
	NXPoint evpt = p->location;
	[self convertPoint: &evpt fromView: 0];
	MiscTableBorder* const b = [self borderFor:tracker_border];
	MiscCoord_V slot = b->visualForOffset(
				MiscPixels([self border:tracker_border pointX:&evpt]) );
	int const WANTED = (NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK | NX_TIMERMASK);

	[focuser stopFocus];
	[tracker mouseDown: p atPos: slot];
	[self borderFor: [self otherBorder:tracker_border]]->selectionSet().empty();
	[scroll reflectSelection];

	int old_mask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	NXTrackingTimer* timer = 0;
	startTimer( timer );
	NXEvent lastEvent = *p;

	for (;;)
		{
		p = [NXApp getNextEvent: WANTED];
		if (p == 0 || p->type == NX_MOUSEUP)
			break;
		else if (p->type == NX_TIMER)
			[self autoscroll: &lastEvent];
		else
			lastEvent = *p;

		NXPoint new_loc = lastEvent.location;
		[self convertPoint:&new_loc fromView:0];
		MiscPixels const offset =
				MiscPixels( [self border:tracker_border pointX:&new_loc] );
		MiscCoord_V const new_slot =
				(offset < [self border:tracker_border rectMaxX:&bounds] ?
				b->visualForOffset(offset) : b->count());
		if (new_slot != slot)
			{
			slot = new_slot;
			[tracker mouseDragged: p atPos: slot];
			[scroll reflectSelection];
			}
		}

	stopTimer( timer );
	[window setEventMask:old_mask];

	[tracker mouseUp: p atPos: slot];
	[scroll reflectSelection];
	[scroll border: tracker_border setCursor:
				b->visualToPhysical([self constrainSlot:slot inBorder:b])];
	[scroll selectText: self];
	[focuser startFocus];

	if ([scroll isEnabled])
		{
		if (doubleClicked)
			[scroll sendDoubleAction];
		else
			[scroll sendAction];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - moveFocusBy:
//-----------------------------------------------------------------------------
- (void) moveFocusBy: (int) delta
	{
	MiscTableBorder* const b = [self borderFor: tracker_border];
	int const lim = b->count();
	if ([focuser isShowingFocus] && lim > 0)
		{
		MiscCoord_V focusSlot = b->getCursor() + delta;
		if (focusSlot < 0)
			focusSlot = lim - 1;
		else if (focusSlot >= lim)
			focusSlot = 0;
		b->setCursor( focusSlot );
		[self border:tracker_border scrollToVisible:
						b->visualToPhysical(focusSlot)];
		[self setFocusFrame];
		}
	}


//-----------------------------------------------------------------------------
// - gotoNextText
//-----------------------------------------------------------------------------
- (void) gotoNextText
	{
	id targ = [scroll nextText];
	if (targ && [targ respondsTo: @selector(selectText:)])
		[targ selectText: scroll];
	}


//-----------------------------------------------------------------------------
// - gotoPreviousText
//-----------------------------------------------------------------------------
- (void) gotoPreviousText
	{
	id targ = [scroll previousText];
	if (targ && [targ respondsTo: @selector(selectText:)])
		[targ selectText: scroll];
	}


//-----------------------------------------------------------------------------
// - keyboardSelect:
//-----------------------------------------------------------------------------
- (void) keyboardSelect: (NXEvent const*)p
	{
	[tracker keyDown: p atPos: [self borderFor:tracker_border]->getCursor()];
	[scroll borderClearSelection:[self otherBorder: tracker_border]];
	[scroll reflectSelection];
	if ([scroll isEnabled])
		[scroll sendAction];
	}


//-----------------------------------------------------------------------------
// - keyboardPerform
//-----------------------------------------------------------------------------
- (void) keyboardPerform
	{
	if ([scroll isEnabled])
		[scroll sendDoubleAction];
	}


//-----------------------------------------------------------------------------
// - keyDown:
//-----------------------------------------------------------------------------
- keyDown: (NXEvent*) p
	{
	enum
		{
		K_TAB	= '\t',
		K_BTAB	= 0x19,
		K_RETURN= '\r',
		K_SPACE = ' ' ,
		K_LEFT	= 0xac,
		K_UP	= 0xad,
		K_RIGHT = 0xae,
		K_DOWN	= 0xaf,
		};

	if ((p->flags & NX_COMMANDMASK) == 0)
		{
		switch (p->data.key.charCode)
			{
			case K_UP			: [self moveFocusBy: -1			]; break;
			case K_DOWN			: [self moveFocusBy:  1			]; break;
			case K_LEFT			: [self moveFocusBy: -1			]; break;
			case K_RIGHT		: [self moveFocusBy:  1			]; break;
			case K_TAB			: [self gotoNextText			]; break;
			case K_BTAB			: [self gotoPreviousText		]; break;
			case K_SPACE		: [self keyboardSelect: p		]; break;
			case K_RETURN		: [self keyboardPerform			]; break;
			}
		}
	return self;
	}

@end
